/* 
 * $Id: edit.c 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
 *
 * Copyright (c) 2006, 2007, 2008 Prabhu Gurumurthy <pgurumur@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "edit.h"

/* Again minimalistic documentation */
static char _PyEdit_ConnectDoc[] = "Editline for python";

/* Init editline object group */
PyTypeObject PyEdit_ObjectType = {
   PyObject_HEAD_INIT(NULL)
   0,
   "edit",
   sizeof(PyEdit_Object),
   0,

   (destructor)_PyEdit_Object_DeAlloc,
   NULL,
   (getattrfunc)_PyEdit_GetAttr,
   (setattrfunc)_PyEdit_SetAttr,
   NULL,
   NULL,

   NULL,
   NULL,
   NULL,

   NULL,
   NULL,
   NULL,
   NULL,
   NULL,

   NULL,

   Py_TPFLAGS_DEFAULT,

   (char *)_PyEdit_ConnectDoc,
   (traverseproc)_PyEdit_Traverse,

   NULL,

   NULL,

   0,

   NULL,
   NULL,

   (struct PyMethodDef *)PyEdit_Methods,
   (EL_MEMBER(*))_PyEdit_ObjectMemberList,
   NULL,
   NULL,

   NULL,
   0,
   NULL,
   0,

   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   0,
   0,
   0
};

/* module hooks */
DL_EXPORT(void)
initeditline(void)
{
   PyObject *module, *dict, *version;

   /* init the module */
   module = Py_InitModule4("editline", PyEdit_Methods, _PyEdit_Doc,
         (PyObject *)NULL, PYTHON_API_VERSION); 
   
   if ((dict = PyModule_GetDict(module)) == NULL)
      Py_FatalError("PyModule_GetDict()");

   /* version of the module */
   version = PyString_FromString(_PyEdit_Version);
   if (version == NULL || PyDict_SetItemString(dict, _PyEdit_Version, version))
      Py_FatalError("could not initialize version string");
   
   Py_DECREF(version);
   
   /* Init edit error */
   PyEdit_Error = Py_BuildValue("s", "EditLineError");
   if (PyEdit_Error == NULL || 
         PyDict_SetItemString(dict, "error", PyEdit_Error))
      Py_FatalError("coult not initialize editline");
   
   Py_DECREF(PyEdit_Error); 

   /* Set object type function */
   PyEdit_ObjectType.ob_type = &PyType_Type;
   PyEdit_ObjectType.tp_alloc = PyType_GenericAlloc;
   PyEdit_ObjectType.tp_new = PyType_GenericNew;
   PyEdit_ObjectType.tp_free = _PyObject_GC_Del;
   
   if (PyDict_SetItemString(dict, "editLineObject",
            (PyObject *)&PyEdit_ObjectType))
      Py_FatalError("could not initialize editline object type"); 
   
   Py_INCREF(&PyEdit_ObjectType); 
   fflush(stdout);
}

static void
_PyEdit_Object_DeAlloc(PyEdit_Object *self)
{
   PyObject *obj;
   if (self->edit) {
      obj = PyEdit_Delete(self, NULL);
      Py_XDECREF(obj);
   }
}

static int
_PyEdit_Traverse(PyEdit_Object *self, visitproc visit, void *arg)
{
   int ix;
   int retval = 0;

   if (self->converter) {
      if (!(ix = visit(self->converter, arg)))
         retval = ix;
   }
   
   return retval;
}

static PyObject *
_PyEdit_GetAttr(PyEdit_Object *self, char *name)
{
   PyObject *retval = NULL;

   retval = Py_FindMethod(PyEdit_Methods, (PyObject *)self, name);
   if (retval == NULL)
      PyErr_SetString(PyExc_AttributeError, name);

   return retval;
}

static int
_PyEdit_SetAttr(PyEdit_Object *self, char *name, PyObject *obj)
{
   if (obj == NULL) {
      PyErr_SetString(PyExc_AttributeError, "cannot delete attributes");
      return (-1);
   }

   EL_MEMBER(*list);
   for (list = _PyEdit_ObjectMemberList; list->name; list++)
      if (strcmp(list->name, name) == 0)
         return PyMember_SetOne((char *)self, list, obj);

   PyErr_SetString(PyExc_AttributeError, name);
   return -1;
}

static PyObject *
PyEdit_Init(PyObject *self, PyObject *args, PyObject *keywords)
{
   /* init python editline */
   int histSize;
   char *editor;
   char *editrc;
   char *histfile;
   
   /* Setup the main object */
   PyEdit_Object *editObj = EL_ALLOC(PyEdit_Object, PyEdit_ObjectType);
   if (editObj == NULL) {
      PyErr_SetString(PyEdit_Error, strerror(errno));
      Py_Exit(1);
   }

   /* init editline */
   editObj->edit = el_init("editline", stdin, stdout, stderr);
   if (editObj->edit == NULL) {
      PyErr_SetString(PyEdit_Error, strerror(errno));
      Py_Exit(1);
   }

   /* Init editline history */
   editObj->hist = history_init();
   if (editObj->hist == NULL) {
      PyErr_SetString(PyEdit_Error, strerror(errno));
      Py_Exit(1);
   }

   editObj->list = PyList_New(0);

   /* 
    * Keywords that this function accepts as part of configuring
    * editline
    */
   static char *keywordsList[] = { 
      "history", 
      "editor", 
      "prompt", 
      "editrc",
      "histfile",
      NULL }; 

   /* parse 'em all */
   int retval = PyArg_ParseTupleAndKeywords(
         args, keywords, "issss", keywordsList, &histSize, &editor, 
         &editObj->prompt, &editrc, &histfile);
   if (retval) {

      /* Set history size */
      history(editObj->hist, &editObj->hev, H_SETSIZE, histSize);

      /* set whether the adjacent history objects should be unique */
      history(editObj->hist, &editObj->hev, H_SETUNIQUE, 1);

      /* Set the history object */
      el_set(editObj->edit, EL_HIST, history, editObj->hist);

      /* Set the editor */
      el_set(editObj->edit, EL_EDITOR, editor);

      /* Set the prompt */
      el_set(editObj->edit, EL_PROMPT, get_edit_prompt);

      /* Set the editrc file */
      struct stat sb;
      if (editrc) {
         memset(&sb, 0, sizeof(struct stat));

         if (stat(editrc, &sb) < 0)
            close(open(editrc, O_RDWR | O_CREAT | O_SYNC, 0644));
         else 
            close(open(editrc, O_RDWR | O_APPEND | O_SYNC, 0644));

         el_source(editObj->edit, editrc);
      }

      /* Set the history file */
      if (histfile) {
         memset(&sb, 0, sizeof(struct stat));

         if (stat(histfile, &sb) < 0)
            close(open(histfile, O_RDWR | O_CREAT | O_SYNC, 0644));
         else
            close(open(histfile, O_RDWR | O_APPEND | O_SYNC, 0644));
         
         editObj->histfile = histfile;
         if (sb.st_size)
            history(editObj->hist, &editObj->hev, H_LOAD, editObj->histfile);
      }
   }
   else {
      /* Use default values, except history file */
      editObj->prompt = "editline > ";
      history(editObj->hist, &editObj->hev, H_SETSIZE, DEFAULT_HISTSIZE);
      history(editObj->hist, &editObj->hev, H_SETUNIQUE, 1);
      el_set(editObj->edit, EL_HIST, history, editObj->hist);
      el_set(editObj->edit, EL_EDITOR, DEFAULT_EDITOR);
      el_set(editObj->edit, EL_PROMPT, get_edit_prompt);
      el_source(editObj->edit, NULL);
   }

   /* Use internal signal within editline */
   el_set(editObj->edit, EL_SIGNAL, 1);

   /* Register PyEdit_Object within Editline */
   el_set(editObj->edit, EL_CLIENTDATA, editObj);

   /* return the edit object */
   return (PyObject *)editObj;
}

/* Function called before python exits, resets editline and history */
static PyObject *
PyEdit_Delete(PyEdit_Object *self, PyObject *args)
{
   PyObject *retval = NULL;

   if (self) {
      if (self->edit) {
         el_reset(self->edit);
         el_end(self->edit);
         self->edit = NULL;
      }

      if (self->hist) {
         if (self->histfile)
            history(self->hist, &self->hev, H_SAVE, self->histfile);

         history_end(self->hist);
         self->hist = NULL;
      }

      Py_INCREF(Py_None);
      retval = Py_None;
   }

   return retval;
}

/*
 * used for EL_PROMPT
 */
char *
get_edit_prompt(EditLine *el)
{
   PyEdit_Object *new;
   char *retval = NULL;

   el_get(el, EL_CLIENTDATA, &new);
   if (new) {
      if (new->prompt) 
         retval = new->prompt;
   }
   else
      retval = "editline > ";

   return retval;
}

/*
 * command line to get the string, optionally, a new prompt can be set
 */
static PyObject *
PyEdit_Gets(PyEdit_Object *self, PyObject *args)
{
   PyObject *retval = Py_None;

   char *prompt;
   /* If I have a new prompt, set it */
   if (args)
       PyArg_ParseTuple(args, "s", &prompt);

   size_t len = strlen(prompt);
   if (len > 1)
      self->prompt = prompt;

   size_t length = 0;
   char *buffer = (char *)el_gets(self->edit, (int *)&length);
   if (buffer) {
      size_t bufLen = strlen(buffer);
      if (bufLen != length)
         length = bufLen;

      if (length > 1) {
         buffer[bufLen - 1] = '\0';

         /* build the return value */
         retval = Py_BuildValue("s", buffer);

         /* Add to history */
         history(self->hist, &self->hev, H_ENTER, buffer);
      }
   }
   else {
      el_reset(self->edit);
      Py_INCREF(Py_None);
   }

   return retval;
}

/*
 * Gets the list of history items or just one entry from history
 */
static PyObject *
PyEdit_History(PyEdit_Object *self, PyObject *args)
{
   int count = 0;
   int element = 0;
   
   PyObject *retval = PyList_New(0);

   if (args)
      PyArg_ParseTuple(args, "i", &element);

   if (element) {
      history(self->hist, &self->hev, H_SET, element);
      history(self->hist, &self->hev, H_CURR, element);
      if (self->hev.str)
         PyList_Append(retval, Py_BuildValue("s", self->hev.str));
   }
   else {
      history(self->hist, &self->hev, H_GETSIZE, count);
      int max = self->hev.num;

      if (max) {
         for (count = 1; count <= max; count++) {
            history(self->hist, &self->hev, H_SET, count);
            history(self->hist, &self->hev, H_CURR, count);
            PyList_Append(retval, Py_BuildValue("s", self->hev.str));
         }
      }
   }

   return retval;
}

static PyObject *
PyEdit_SetCompleter(PyEdit_Object *self, PyObject *args)
{
   PyObject *list;
   PyObject *retval = NULL;
   int ret = 1;

   if (args)
      PyArg_ParseTuple(args, "O", &list);

   if (PyList_Check(list)) {
      self->list = list;

      el_set(self->edit, EL_ADDFN, "edit-complete",
            "Context senstive argument completion",
            PyEdit_Complete);
      el_set(self->edit, EL_BIND, "^I", "edit-complete", NULL);
      ret = 0;
   }

   retval = Py_BuildValue("i", ret);
   return retval;
}

static unsigned char
PyEdit_Complete(EditLine *el, int ch)
{
   PyEdit_Object *new;
   int retval;

   const LineInfo *lf = el_line(el);
   size_t length = lf->lastchar - lf->buffer;

   ch = ch;

   el_get(el, EL_CLIENTDATA, &new);
   if (new->list) {
      if (length >= sizeof(lf->buffer))
         retval = CC_ERROR;
      else {
         int ix = 0;
         for (ix = 0; ix < (int)PyList_Size(new->list); ix++) {
            PyObject *str = PyList_GetItem(new->list, (Py_ssize_t)ix);
            char *cmd = PyString_AsString(str);
            int cmp = strncmp(cmd, lf->buffer, sizeof(lf->buffer));
            if (cmp > 100) {
               el_deletestr(el, 0);
               if (el_insertstr(el, cmd) == -1)
                  retval = CC_ERROR;
               else
                  retval = CC_REDISPLAY;
            }
         }
         retval = CC_REDISPLAY;
      }
   }

   return retval;
}
